package com.lx.boot.web;

import com.lx.annotation.Note;
import com.lx.boot.OS;
import com.lx.boot.cache.util.CacheUtils;
import com.lx.boot.web.annotation.Auth;
import com.lx.boot.web.config.RequestInfo;
import com.lx.boot.web.config.WebProperties;
import com.lx.boot.web.filter.ApiEncryptFilter;
import com.lx.boot.web.filter.DefaultApiEncryptFilter;
import com.lx.constant.DefaultResult;
import com.lx.constant.DefaultStatusCode;
import com.lx.entity.Result;
import com.lx.entity.UserInfo;
import com.lx.util.LX;
import com.lx.util.exception.ResultServiceException;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.lx.constant.DefaultBaseConstant.*;


//说明: 验签鉴权 开启跨域
/**{ ylx } 2022/3/10 11:47 */
public class SecurityFilter implements Filter {
    private Logger log = LoggerFactory.getLogger(SecurityFilter.class);

    private AuthUtil authUtil;

    public SecurityFilter(AuthUtil authUtil) {
        this.authUtil = authUtil;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        long startTime = System.currentTimeMillis();
        String url = request.getServletPath();
        try {
            // 如果是OPTIONS则结束请求
            if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
                origin(response, request);
                response.setStatus(HttpStatus.NO_CONTENT.value());
                return;
            }
            HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);
            if (handlerExecutionChain == null){
                // 不是接口
                filterChain.doFilter(request,response);
                return;
            }
            //设置日志ID
            OS.setLogTraceId();
            //设置企业ID
            String firmId = request.getHeader(FIRM_ID);
            OS.setFirmId(firmId);
            // 获取请求信息
            RequestInfo requestInfo = new RequestInfo(request,response);
            OS.setRequestInfo(requestInfo); //设置缓存信息
            WebProperties webProperties = OS.getBeanProperty(WebProperties.class);
            requestInfo.setWebProperties(webProperties);
            // 接口注解
            Auth auth = getApiAnnotation(handlerExecutionChain, Auth.class);
            requestInfo.setAuth(auth);
            if (requestInfo.isAllowCrossdomain()){
                //允许跨域
                origin(response, request);
            }
            if (requestInfo.isExcludeUrl()){//不需要经过后续验证的接口
                filterChain.doFilter(request,response);
                return;
            }
            if (auth != null && auth.closeLogTrace()){
                //特殊接口上传日志不设置日志id
                OS.removeTraceId();
            }
            if (requestInfo.isFirmIdRequired()){
                LX.exObj(firmId,"请更新为最新版本!");
            }
            // 是否进行csrf检查
            if (requestInfo.isCsrf() && !csrf(request)){
                return;
            }
            //对用户信息进行判断
            UserInfo userInfo = authUtil.getUserInfo(request);
            OS.setUserInfo(userInfo);
            if (!checkAuth(auth,webProperties, userInfo, url,request,response)){
                return;
            }
            //进行参数加解密
            ApiEncryptFilter apiEncryptFilter = getApiFilter(webProperties, auth, url);
            apiEncryptFilter.filter(userInfo,filterChain);
            // 调用接口完毕完毕
            long interval = System.currentTimeMillis() - startTime;
            if (interval>3000){
                log.info("url==>{} ,请求完成警告,耗时  {}  毫秒",url, interval);
            }else{
                log.info("url==>{} ,请求完成,耗时  {}  毫秒",url, interval);
            }
        } catch (Exception e) {
            log.error("过滤器错误:",e);
            if (e instanceof ResultServiceException rse){
                OS.responseErrorResult(rse.getResult());
            }else{
                OS.responseErrorResult(new Result(DefaultStatusCode.ERROR,e.getCause()==null?e.getMessage():e.getCause().getMessage()));
            }
        }finally {
            MDC.remove("requestId");
            OS.remove();
        }
    }

    @Note("跨域处理")
    private void origin(HttpServletResponse response, HttpServletRequest request) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "*");
    }

    @Note("csrf检查")
    protected boolean csrf(HttpServletRequest request) throws IOException {
        Map<String, String> headersInfo = getHeadersInfo(request);
        String timestamp = request.getHeader("timestamp");
        String nonce = request.getHeader("nonce");
        String token = request.getHeader("Authorization");
        if (LX.isEmpty(timestamp)||LX.isEmpty(nonce) || !LX.md5(timestamp+token).equals(nonce.substring(10))){
            log.info("触发csrf验证!");
            OS.responseErrorResult(DefaultResult.TOKERPAST.getResult());
            return false;
        }
        return ApiEncryptFilter.checkApiTimeAndRepeat(timestamp,nonce);
    }

    @Note("检查登录信息")
    private boolean checkAuth(Auth auth,WebProperties webProperties, UserInfo userInfo, String url, HttpServletRequest request, HttpServletResponse response) throws InstantiationException, IllegalAccessException {
        if (!(LX.isNotEmpty(userInfo) || webProperties.isNotAuthUrl(url) || (auth != null && !auth.value()) || OS.isDevRequest())){
            // 用户不为空 或 无需登录的接口 或 注解指定无需登录 或 开发环境
            log.warn("url:{},[firmId:{} Authorization:{}], {}",url,request.getHeader(FIRM_ID) , request.getHeader(AUTHORIZATION), "请重新登录! 请检查是否使用 AuthUtil.createToken/createJwtToken 创建用户登录信息!");
            OS.responseErrorResult(DefaultResult.TOKERPAST.getResult());
            return false;
        }
        if (auth == null){
            return true;
        }
        // 判断IP
        if (LX.isNotEmpty(auth.ipWhiteListRegexApplicationKey())){
            String ip = OS.getIp(request);
            String ipWhiteList = OS.getProperty(auth.ipWhiteListRegexApplicationKey());
            if (LX.isEmpty(ip) || LX.isEmpty(ipWhiteList) || !ip.matches(ipWhiteList)){
                log.info("调用IP异常: ip:{}, 接口IP注解配置:{}, 实际配置:{}",ip, auth.ipWhiteListRegexApplicationKey(),ipWhiteList);
                OS.responseErrorResult(DefaultResult.IP_LIMIT.setMessage("调用IP异常:"+ip));
                return false;
            }
        }
        // 根据IP限制方法调用次数
        if (LX.isNotEmpty(auth.callNumberByIp())){
            String ip = OS.getIp(request);
            if (!OS.allowAccessByCallFrequency(ip+":"+url, auth.callNumberByIp())){
                log.info("点击太快,请稍后再试! ip:{}, url:{}, 用户信息:{}",ip, url,userInfo);
                OS.responseErrorResult(DefaultResult.CALL_LIMIT.setMessage("点击太快,请稍后再试!:"+auth.callNumberByIp()));
                return false;
            }
        }
        if (LX.isNotEmpty(userInfo)){
            // 判断是否有用户类型权限
            if (LX.isNotEmpty(auth.allowUserTypeRegex())){
                if (LX.isEmpty(userInfo.getUserType()) || !userInfo.getUserType().matches(auth.allowUserTypeRegex())){
                    log.info("权限不足 用户信息:{}, 接口注解配置:{}",userInfo, auth.allowUserTypeRegex());
                    OS.responseErrorResult(DefaultResult.ACCESSERROR.getResult());
                    return false;
                }
            }
            //判断是否有接口权限 超级管理员不判断
            if (auth.allowPermissions() && !authUtil.checkUserPermissions(url,userInfo)){
                log.info("权限不足 用户信息:{}, 不具备接口权限!",userInfo);
                OS.responseErrorResult(DefaultResult.ACCESSERROR.getResult());
                return false;
            }
            // 根据用户ID限制方法调用次数
            if (LX.isNotEmpty(auth.callNumberByUser())){
                if (!OS.allowAccessByCallFrequency(userInfo.getUserId()+":"+url, auth.callNumberByUser())){
                    log.info("点击太快,请稍后再试! url:{}, 用户信息:{}", url,userInfo);
                    OS.responseErrorResult(DefaultResult.CALL_LIMIT.setMessage("点击太快,请稍后再试!:"+auth.callNumberByUser()));
                    return false;
                }
            }
        }
        return true;
    }


    @Override
    public void destroy() {
    }

    @Note("获取controller的接口信息")
    public static HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest servletRequest) {
        RequestMappingHandlerMapping handlerMapping = OS.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        // 获取注解
        try {
            return handlerMapping.getHandler(servletRequest);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Note("获取controller的注解")
    private <T extends Annotation>T getApiAnnotation(HandlerExecutionChain handlerExecutionChain, Class<T> t) {
        Object handler = handlerExecutionChain.getHandler();
        if (handler instanceof HandlerMethod handlerMethod){
            return Optional.ofNullable(handlerMethod.getMethod().getAnnotation(t)).orElse(handlerMethod.getBean().getClass().getAnnotation(t));
        }
        return null;
    }
    @Note("获取controller的注解")
    private <T extends Annotation>T getApiAnnotation(HttpServletRequest servletRequest, Class<T> t) {
        RequestMappingHandlerMapping handlerMapping = OS.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        // 获取注解
        try {
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
            if (mappingHandler != null){
                Object handler = mappingHandler.getHandler();
                if (handler instanceof HandlerMethod handlerMethod){
                    return Optional.ofNullable(handlerMethod.getMethod().getAnnotation(t)).orElse(handlerMethod.getBean().getClass().getAnnotation(t));
                }
            }else{
                throw new ResultServiceException(DefaultResult.NO_HADNLER_ERR.setMessage("接口 [" +servletRequest.getMethod()+ " " + servletRequest.getRequestURI() + "] 不存在"));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    //说明: 获取请求头
    /**{ ylx } 2021/4/1 11:04 */
    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<String, String>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }


    @Note("获取接口过滤器 对出入参进行处理")
    public ApiEncryptFilter getApiFilter(WebProperties webProperties, Auth auth, String url) {
        ApiEncryptFilter apiEncryptFilter;
        if (webProperties.isNotEncryptionUrl(url)){
            apiEncryptFilter = OS.getBean(DefaultApiEncryptFilter.class);
        }else if (auth != null && !ApiEncryptFilter.class.equals(auth.apiFilter())){
            apiEncryptFilter = OS.getBean(auth.apiFilter());
        }else{
            apiEncryptFilter = OS.getBean(webProperties.getApiFilterName(), ApiEncryptFilter.class);
        }
        OS.setApiEncryptFilter(apiEncryptFilter);
        return apiEncryptFilter;
    }

}
